package debtbundler.domain.bll;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;

import debtbundler.domain.entity.Payment;
import debtbundler.domain.entity.SharedCost;
import debtbundler.domain.entity.MoneyPool;
import debtbundler.domain.entity.Person;
import debtbundler.domain.entity.Share;
import debtbundler.domain.entity.Transaction;
import debtbundler.domain.entity.impl.EntityManager;

public class Utils {
	
	/**
	 * From a collection of transactions, finds those related to a given sharedCost 
	 * (i.e. the sharedCost is the "from" or "to" end of a transaction). 
	 * 
	 * @param transactions	the collection to search from 
	 * @param sharedCost			the sharedCost whose 
	 * @return				transactions related to <code>sharedCost</code>
	 */
	public static <T extends Transaction> ArrayList<T> findTransactionsOfPool(Collection<T> transactions, MoneyPool pool) {
		ArrayList<T> retVal = new ArrayList<T>();
		for (T tr : transactions) {
			if (tr.getFromPool().equals(pool) || tr.getToPool().equals(pool) ) {
				retVal.add(tr);
			}
		}
		return retVal;
	}
	
	/**
	 * Aggregates given flows into one flow having the same total value.
	 * <p>
	 * This operation removes the input flows from the person and the person 
	 * and replaces them with one flow. All the flows must be between the same person and person. 
	 * If the collection is empty, a flow having zero value is created. 
	 * 
	 * @param flows		flows to aggregate
	 * @return			aggregated flow, <code>null</code> if <code>flows</code> is empty
	 * @throws IllegalArgumentException
	 * 	if all flows are not between the same person and person
	 */
	public static Payment aggregateFlows(Collection<Payment> flows) {
		if (flows.isEmpty()) {
			return null;
		}
		
		Payment firstFlow = flows.iterator().next(); 
		Person thePerson = firstFlow.getPerson();
		SharedCost thePool = firstFlow.getSharedCost();
		
		double totalValue = 0;
		for (Payment flow : flows) {
			if ( !thePerson.equals(flow.getPerson()) || !thePool.equals(flow.getSharedCost()) ) {
				throw new IllegalArgumentException("All flows not between same person and person");
			}
			totalValue += flow.getValue();
		}
		
		for (Payment flow : flows) {
			thePerson.removeTransaction(flow);
		}
		
		Payment flow = EntityManager.createPayment(thePerson, thePool);
		flow.setValue(totalValue);
		return flow;
		
	}

	/**
	 * Calculates the total value of given transactions. Note that this
	 * doesn't take the directions of transactions into account.
	 *  
	 * @param transactions	transactions whose total value to calculate
	 * @return				the total value of the transactions
	 */
	public static double sum(Collection<? extends Transaction> transactions) {
		if (transactions == null) return 0;
		
		double sum = 0;
		for (Transaction tr : transactions) {
			sum += tr.getValue();
		}
		return sum;
	}

	/**
	 * Balances a shared cost with with equal shares for given participants.
	 * <p> 
	 * Only persons defined in the participants set are to share the costs - 
	 * people who pay costs do not automatically participate in sharing the them.
	 *
	 * @param sharedCost	
	 * 	the cost to share
	 * @param participants
	 * 	persons to share the costs
	 * @return
	 * 	the value of the share per participant, <code>0</code> if there are no
	 * 	participants
	 */
	public static double balanceByEqualSharing(SharedCost sharedCost, Set<Person> participants) {
		if (participants == null || participants.size() == 0) {
			return 0;
		}
		
		// Clear old shares
		for (Share share : sharedCost.getShares()) {
			sharedCost.removeTransaction(share);
		}
		
		// Calculate new flows from the sharedCost to the participants
		double costPerPerson = sharedCost.getValue() / participants.size();
		for (Person person : participants) {
			Share share = EntityManager.createShare(person, sharedCost);
			share.setValue(costPerPerson);
		}
		
		return costPerPerson;
	}	
	
}
